package com.css.influence.util;

import com.css.influence.vo.GeoPoint;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.util.AffineTransformation;
import org.locationtech.jts.util.GeometricShapeFactory;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.jts.geom.util.AffineTransformation;

import java.math.BigDecimal;

/**
 * @author Cao.Jun
 */
public class EnvelopeFactory {
    protected GeometryFactory geomFact;
    protected PrecisionModel precModel;
    protected EnvelopeFactory.Dimensions dim;
    protected int nPts;
    protected double rotationAngle;

    public EnvelopeFactory() {
        this(new GeometryFactory());
    }

    public EnvelopeFactory(GeometryFactory geomFact) {
        this.precModel = null;
        this.dim = new EnvelopeFactory.Dimensions();
        this.nPts = 100;
        this.rotationAngle = 0.0;
        this.geomFact = geomFact;
        this.precModel = geomFact.getPrecisionModel();
    }

    public void setEnvelope(Envelope env) {
        this.dim.setEnvelope(env);
    }

    public void setBase(Coordinate base) {
        this.dim.setBase(base);
    }

    public void setCentre(Coordinate centre) {
        this.dim.setCentre(centre);
    }

    public void setNumPoints(int nPts) {
        this.nPts = nPts;
    }

    public void setSize(double size) {
        this.dim.setSize(size);
    }

    public void setWidth(double width) {
        this.dim.setWidth(width);
    }

    public void setHeight(double height) {
        this.dim.setHeight(height);
    }

    public void setRotation(double radians) {
        this.rotationAngle = radians;
    }

    protected Geometry rotate(Geometry geom) {
        if (this.rotationAngle != 0.0) {
            AffineTransformation trans = AffineTransformation.rotationInstance(this.rotationAngle, this.dim.getCentre().x, this.dim.getCentre().y);
            geom.apply(trans);
        }

        return geom;
    }


    //包络图
    public Polygon createEnvelope(double length) {
        Envelope env = this.dim.getEnvelope();
        double xRadius = env.getWidth() / 2.0;
        double yRadius = env.getHeight() / 2.0;
        double centreX = env.getMinX() + xRadius;
        double centreY = env.getMinY() + yRadius;
        Coordinate[] pts = new Coordinate[this.nPts + 1];
        int iPt = 0;

//        double distance = (length / 2 - env.getWidth())/1000;
        double distance = (length / 2 )/1000;

        for(int i = 0; i < this.nPts; ++i) {
            double ang = (double)i * (6.283185307179586 / (double)this.nPts);
            double x = xRadius * Math.cos(ang) + centreX;
            double y = yRadius * Math.sin(ang) + centreY;

            if(BigDecimal.valueOf(x).compareTo(BigDecimal.valueOf(centreX)) > 0 ){
                GeoPoint geoPoint = translatePoint(new GeoPoint(x, y), distance, 0);
                pts[iPt++] = this.coord(geoPoint.getLatitude(), geoPoint.getLongitude());
            }else{
                GeoPoint geoPoint = translatePoint(new GeoPoint(x, y), -distance, 0);
                pts[iPt++] = this.coord(geoPoint.getLatitude(), geoPoint.getLongitude());
            }

        }

        pts[iPt] = new Coordinate(pts[0]);
        LinearRing ring = this.geomFact.createLinearRing(pts);
        Polygon poly = this.geomFact.createPolygon(ring);
        return (Polygon)this.rotate(poly);
    }

    public Polygon createSquircle() {
        return this.createSupercircle(4.0);
    }

    public Polygon createSupercircle(double power) {
        double recipPow = 1.0 / power;
        double radius = this.dim.getMinSize() / 2.0;
        Coordinate centre = this.dim.getCentre();
        double r4 = Math.pow(radius, power);
        double y0 = radius;
        double xyInt = Math.pow(r4 / 2.0, recipPow);
        int nSegsInOct = this.nPts / 8;
        int totPts = nSegsInOct * 8 + 1;
        Coordinate[] pts = new Coordinate[totPts];
        double xInc = xyInt / (double)nSegsInOct;

        for(int i = 0; i <= nSegsInOct; ++i) {
            double x = 0.0;
            double y = y0;
            if (i != 0) {
                x = xInc * (double)i;
                double x4 = Math.pow(x, power);
                y = Math.pow(r4 - x4, recipPow);
            }

            pts[i] = this.coordTrans(x, y, centre);
            pts[2 * nSegsInOct - i] = this.coordTrans(y, x, centre);
            pts[2 * nSegsInOct + i] = this.coordTrans(y, -x, centre);
            pts[4 * nSegsInOct - i] = this.coordTrans(x, -y, centre);
            pts[4 * nSegsInOct + i] = this.coordTrans(-x, -y, centre);
            pts[6 * nSegsInOct - i] = this.coordTrans(-y, -x, centre);
            pts[6 * nSegsInOct + i] = this.coordTrans(-y, x, centre);
            pts[8 * nSegsInOct - i] = this.coordTrans(-x, y, centre);
        }

        pts[pts.length - 1] = new Coordinate(pts[0]);
        LinearRing ring = this.geomFact.createLinearRing(pts);
        Polygon poly = this.geomFact.createPolygon(ring);
        return (Polygon)this.rotate(poly);
    }

    public LineString createArc(double startAng, double angExtent) {
        Envelope env = this.dim.getEnvelope();
        double xRadius = env.getWidth() / 2.0;
        double yRadius = env.getHeight() / 2.0;
        double centreX = env.getMinX() + xRadius;
        double centreY = env.getMinY() + yRadius;
        double angSize = angExtent;
        if (angSize <= 0.0 || angSize > 6.283185307179586) {
            angSize = 6.283185307179586;
        }

        double angInc = angSize / (double)(this.nPts - 1);
        Coordinate[] pts = new Coordinate[this.nPts];
        int iPt = 0;

        for(int i = 0; i < this.nPts; ++i) {
            double ang = startAng + (double)i * angInc;
            double x = xRadius * Math.cos(ang) + centreX;
            double y = yRadius * Math.sin(ang) + centreY;
            pts[iPt++] = this.coord(x, y);
        }

        LineString line = this.geomFact.createLineString(pts);
        return (LineString)this.rotate(line);
    }

    public Polygon createArcPolygon(double startAng, double angExtent) {
        Envelope env = this.dim.getEnvelope();
        double xRadius = env.getWidth() / 2.0;
        double yRadius = env.getHeight() / 2.0;
        double centreX = env.getMinX() + xRadius;
        double centreY = env.getMinY() + yRadius;
        double angSize = angExtent;
        if (angSize <= 0.0 || angSize > 6.283185307179586) {
            angSize = 6.283185307179586;
        }

        double angInc = angSize / (double)(this.nPts - 1);
        Coordinate[] pts = new Coordinate[this.nPts + 2];
        int iPt = 0;
        pts[iPt++] = this.coord(centreX, centreY);

        for(int i = 0; i < this.nPts; ++i) {
            double ang = startAng + angInc * (double)i;
            double x = xRadius * Math.cos(ang) + centreX;
            double y = yRadius * Math.sin(ang) + centreY;
            pts[iPt++] = this.coord(x, y);
        }

        pts[iPt++] = this.coord(centreX, centreY);
        LinearRing ring = this.geomFact.createLinearRing(pts);
        Polygon poly = this.geomFact.createPolygon(ring);
        return (Polygon)this.rotate(poly);
    }

    protected Coordinate coord(double x, double y) {
        Coordinate pt = new Coordinate(x, y);
        this.precModel.makePrecise(pt);
        return pt;
    }

    protected Coordinate coordTrans(double x, double y, Coordinate trans) {
        return this.coord(x + trans.x, y + trans.y);
    }

    protected static class Dimensions {
        public Coordinate base;
        public Coordinate centre;
        public double width;
        public double height;

        protected Dimensions() {
        }

        public void setBase(Coordinate base) {
            this.base = base;
        }

        public Coordinate getBase() {
            return this.base;
        }

        public void setCentre(Coordinate centre) {
            this.centre = centre;
        }

        public Coordinate getCentre() {
            if (this.centre == null) {
                this.centre = new Coordinate(this.base.x + this.width / 2.0, this.base.y + this.height / 2.0);
            }

            return this.centre;
        }

        public void setSize(double size) {
            this.height = size;
            this.width = size;
        }

        public double getMinSize() {
            return Math.min(this.width, this.height);
        }

        public void setWidth(double width) {
            this.width = width;
        }

        public double getWidth() {
            return this.width;
        }

        public double getHeight() {
            return this.height;
        }

        public void setHeight(double height) {
            this.height = height;
        }

        public void setEnvelope(Envelope env) {
            this.width = env.getWidth();
            this.height = env.getHeight();
            this.base = new Coordinate(env.getMinX(), env.getMinY());
            this.centre = new Coordinate(env.centre());
        }

        public Envelope getEnvelope() {
            if (this.base != null) {
                return new Envelope(this.base.x, this.base.x + this.width, this.base.y, this.base.y + this.height);
            } else {
                return this.centre != null ? new Envelope(this.centre.x - this.width / 2.0, this.centre.x + this.width / 2.0, this.centre.y - this.height / 2.0, this.centre.y + this.height / 2.0) : new Envelope(0.0, this.width, 0.0, this.height);
            }
        }
    }


    public static GeoPoint translatePoint(GeoPoint point, double distance, double bearing) {
        // 距离转换为经纬度距离
        double distanceInDegrees = distance / 100; // 1度约等于111.32公里

        // 计算新的经度和纬度
        double newLatitude = point.getLatitude() + distanceInDegrees * Math.cos(Math.toRadians(bearing));
        double newLongitude = point.getLongitude() + distanceInDegrees * Math.sin(Math.toRadians(bearing));

        return new GeoPoint(newLatitude, newLongitude);
    }
}
